﻿/* Copyright (C) 2007-2010 STS Soft

   This program is free software; you can redistribute it and/or modify
   it under the terms of the GNU General Public License as published by
   the Free Software Foundation; version 2 of the License.

   This program is distributed in the hope that it will be useful,
   but WITHOUT ANY WARRANTY; without even the implied warranty of
   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
   GNU General Public License for more details.

   You should have received a copy of the GNU General Public License
   along with this program; if not, write to the Free Software
   Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA */

using System;
using System.Collections.Generic;
using System.Text;
using System.Reflection;
using System.Linq.Expressions;
using System.ComponentModel;
using System.Threading;
using System.Globalization;

namespace STSdb.General.Reflection
{
    public class PropertyHelper
    {
        private Func<object, object> getDelegate;
        private Action<object, object> setDelegate;

        public Type OwnerType { get; private set; }
        public string PropertyName { get; private set; }
        public Type PropertyType { get; private set; }

        public PropertyHelper(Type ownerType, string propertyName)
            : this(ownerType, propertyName, null, null)
        {
        }

        public PropertyHelper(Type ownerType, string propertyName, MethodInfo getConvertMethod, MethodInfo setConvertMethod)
        {
            if (string.IsNullOrEmpty(propertyName))
                throw new ArgumentNullException("propertyName");
            if (null == ownerType)
                throw new ArgumentNullException("type");

            PropertyInfo propertyInfo = ownerType.GetProperty(propertyName);
            if (propertyInfo == null)
                throw new ArgumentException(string.Format("Property {0} {1} does not exists ", propertyName, ownerType.ToString()));

            PropertyType = propertyInfo.PropertyType;
            if (propertyInfo.CanRead)
            {
                MethodInfo methodInfo = propertyInfo.GetGetMethod();
                if (methodInfo != null)
                {
                    var param1 = Expression.Parameter(typeof(object), "obj");
                    var obj = Expression.Convert(param1, ownerType);
                    var get = Expression.MakeMemberAccess(obj, propertyInfo);
                    var convert = Expression.Convert(get, typeof(object), getConvertMethod);
                    var lambda = Expression.Lambda<Func<object, object>>(convert, param1);

                    getDelegate = lambda.Compile();
                }
            }

            if (propertyInfo.CanWrite)
            {
                MethodInfo methodInfo = propertyInfo.GetSetMethod();
                if (methodInfo != null)
                {
                    var param1 = Expression.Parameter(typeof(object), "obj");
                    var param2 = Expression.Parameter(typeof(object), "value");
                    var obj = Expression.Convert(param1, ownerType);
                    var value = Expression.Convert(param2, propertyInfo.PropertyType, setConvertMethod);
                    var set = Expression.Call(obj, methodInfo, value);
                    var lambda = Expression.Lambda<Action<object, object>>(set, param1, param2);

                    setDelegate = lambda.Compile();
                }
            }

            OwnerType = ownerType;
            PropertyName = propertyName;
        }

        public object GetValue(object data)
        {
            return getDelegate(data);
        }

        [Obsolete("Instead use PropertyHelperExtensions")]
        public T GetValue<T>(object data)
        {
            return (T)Convert.ChangeType(GetValue(data), typeof(T), CultureInfo.CurrentCulture);
        }

        public void SetValue(object data, object value)
        {
            setDelegate(data, value);
        }

        public override string ToString()
        {
#if !WindowsCE && !PocketPC
            PropertyInfo pi = OwnerType.GetProperty(PropertyName);
            object[] attr = pi.GetCustomAttributes(typeof(DisplayNameAttribute), false);
            if (attr.Length > 0)
                return ((DisplayNameAttribute)attr[0]).DisplayName;
#endif

            return PropertyName;
        }
    }

    public static class PropertyHelperExtensions
    {
        public static double GetDouble(this PropertyHelper self, object data)
        {
            return ((IConvertible)self.GetValue(data)).ToDouble(null);
        }

        public static double GetDoubleViaDecimal(this PropertyHelper self, object data)
        {
            return (double)GetDecimal(self, data);
        }

        public static float GetFloat(this PropertyHelper self, object data)
        {
            return ((IConvertible)self.GetValue(data)).ToSingle(null);
        }

        public static Int16 GetInt16(this PropertyHelper self, object data)
        {
            return ((IConvertible)self.GetValue(data)).ToInt16(null);
        }

        public static UInt16 GetUInt16(this PropertyHelper self, object data)
        {
            return ((IConvertible)self.GetValue(data)).ToUInt16(null);
        }

        public static Int32 GetInt32(this PropertyHelper self, object data)
        {
            return ((IConvertible)self.GetValue(data)).ToInt32(null);
        }

        public static UInt32 GetUInt32(this PropertyHelper self, object data)
        {
            return ((IConvertible)self.GetValue(data)).ToUInt32(null);
        }

        public static Int64 GetInt64(this PropertyHelper self, object data)
        {
            return ((IConvertible)self.GetValue(data)).ToInt64(null);
        }

        public static UInt64 GetUInt64(this PropertyHelper self, object data)
        {
            return ((IConvertible)self.GetValue(data)).ToUInt64(null);
        }

        public static Decimal GetDecimal(this PropertyHelper self, object data)
        {
            return ((IConvertible)self.GetValue(data)).ToDecimal(null);
        }

        public static DateTime GetDateTime(this PropertyHelper self, object data)
        {
            return (DateTime)self.GetValue(data);
        }

        public static string GetString(this PropertyHelper self, object data)
        {
            return (string)self.GetValue(data);
        }

        public static bool GetBoolean(this PropertyHelper self, object data)
        {
            return (bool)self.GetValue(data);
        }
    }
}
